package core

import (
	"log"
	"net"
	"os"
	"strings"
	"syscall"
	"unsafe"

	"golang.org/x/sys/windows"
)

type SYSTEMTIME struct {
	wYear         uint16
	wMonth        uint16
	wDayOfWeek    uint16
	wDay          uint16
	wHour         uint16
	wMinute       uint16
	wSecond       uint16
	wMilliseconds uint16
}

type FILETIME struct {
	dwLowDateTime  uint32
	dwHighDateTime uint32
}

var (
	// Library
	libkrnDll *syscall.DLL

	// Functions
	krn_LocalFileTimeToFileTime *syscall.Proc
	krn_MultiByteToWideChar     *syscall.Proc
	krn_GlobalMemoryStatusEx    *syscall.Proc
)

func init() {
	libkrnDll = syscall.MustLoadDLL("Kernel32.dll")

	krn_LocalFileTimeToFileTime = libkrnDll.MustFindProc("LocalFileTimeToFileTime")
	krn_MultiByteToWideChar = libkrnDll.MustFindProc("MultiByteToWideChar")
	krn_GlobalMemoryStatusEx = libkrnDll.MustFindProc("GlobalMemoryStatusEx")
}

// UTF16PtrToString converts Windows API LPTSTR (pointer to string) to go string
func UTF16PtrToString(s *uint16) string {
	if s == nil {
		return ""
	}
	return syscall.UTF16ToString((*[1 << 29]uint16)(unsafe.Pointer(s))[0:])
}

// UTF16ToStringArray converts list of Windows API NULL terminated strings  to go string array
func UTF16ToStringArray(buf []uint16) []string {
	var strings []string
	nextLineStart := 0
	stringLine := UTF16PtrToString(&buf[0])
	for stringLine != "" {
		strings = append(strings, stringLine)
		nextLineStart += len([]rune(stringLine)) + 1
		remainingBuf := buf[nextLineStart:]
		stringLine = UTF16PtrToString(&remainingBuf[0])
	}
	return strings
}

// BytePtrToString XXX
func BytePtrToString(p *uint8) string {
	return windows.BytePtrToString(p)
}

func AnsiBytePtrToString(p *uint8) (string, error) {
	a := (*[10000]uint8)(unsafe.Pointer(p))
	i := 0
	for a[i] != 0 {
		i++
	}
	n, _, _ := krn_MultiByteToWideChar.Call(0, 0, uintptr(unsafe.Pointer(p)), uintptr(i), uintptr(0), 0)
	if n <= 0 {
		return "", syscall.GetLastError()
	}
	us := make([]uint16, n)
	r, _, _ := krn_MultiByteToWideChar.Call(0, 0, uintptr(unsafe.Pointer(p)), uintptr(i), uintptr(unsafe.Pointer(&us[0])), n)
	if r == 0 {
		return "", syscall.GetLastError()
	}
	return syscall.UTF16ToString(us), nil
}

type VirtualMemoryStat struct {
	// Total amount of RAM on this system
	Total uint64 `json:"total"`

	// RAM available for programs to allocate
	//
	// This value is computed from the kernel specific values.
	Available uint64 `json:"available"`

	// RAM used by programs
	//
	// This value is computed from the kernel specific values.
	Used uint64 `json:"used"`

	// Percentage of RAM used by programs
	//
	// This value is computed from the kernel specific values.
	UsedPercent float64 `json:"usedPercent"`

	// This is the kernel's notion of free memory; RAM chips whose bits nobody
	// cares about the value of right now. For a human consumable number,
	// Available is what you really want.
	Free uint64 `json:"free"`
}

func VirtualMemory() (*VirtualMemoryStat, error) {
	type memoryStatusEx struct {
		cbSize                  uint32
		dwMemoryLoad            uint32
		ullTotalPhys            uint64 // in bytes
		ullAvailPhys            uint64
		ullTotalPageFile        uint64
		ullAvailPageFile        uint64
		ullTotalVirtual         uint64
		ullAvailVirtual         uint64
		ullAvailExtendedVirtual uint64
	}

	var memInfo memoryStatusEx
	memInfo.cbSize = uint32(unsafe.Sizeof(memInfo))
	mem, _, _ := krn_GlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&memInfo)))
	if mem == 0 {
		return nil, windows.GetLastError()
	}

	ret := &VirtualMemoryStat{
		Total:       memInfo.ullTotalPhys,
		Available:   memInfo.ullAvailPhys,
		Free:        memInfo.ullAvailPhys,
		UsedPercent: float64(memInfo.dwMemoryLoad),
	}
	ret.Used = ret.Total - ret.Available

	return ret, nil
}

type IpAdapterInfo struct {
	*syscall.IpAdapterInfo
}

func (*IpAdapterInfo) ptrToString(p *uint8) string {
	value, err := AnsiBytePtrToString(p)
	if err != nil {
		value = BytePtrToString(p)
	}

	return value
}

func (c *IpAdapterInfo) getPDHCounterName(v *syscall.IpAdapterInfo) (string, string) {
	descriptionName := c.ptrToString(&v.Description[0])

	name := descriptionName
	name = strings.Replace(name, "(", "[", -1)
	name = strings.Replace(name, ")", "]", -1)
	name = strings.Replace(name, "#", "_", -1)
	name = strings.Replace(name, "/", "_", -1)
	name = strings.Replace(name, `\`, "_", -1)

	return descriptionName, name
}

func (c *IpAdapterInfo) GetAllDescription() []string {
	var result = make([]string, 0)
	for p := c.IpAdapterInfo; p != nil; p = p.Next {
		name := c.ptrToString(&c.Description[0])
		result = append(result, name)
	}

	return result
}

func (c *IpAdapterInfo) GetAllPDHCounterName() map[string]string {
	var result = map[string]string{}
	for p := c.IpAdapterInfo; p != nil; p = p.Next {
		k, v := c.getPDHCounterName(p)
		result[k] = v

		log.Println(p.Index, k, p.Type, p.ComboIndex, c.ptrToString(&p.AdapterName[0]))
	}

	return result
}

// GetAdaptersInfo 根据syscall包获取网卡信息 (底层原理是通过windows dll api获取)
func GetAdaptersInfo() (*IpAdapterInfo, error) {
	bTmp := make([]byte, 1000)
	length := uint32(len(bTmp))
	adtr := (*syscall.IpAdapterInfo)(unsafe.Pointer(&bTmp[0]))

	if err := syscall.GetAdaptersInfo(adtr, &length); err == syscall.ERROR_BUFFER_OVERFLOW {
		bTmp = make([]byte, length)
		adtr = (*syscall.IpAdapterInfo)(unsafe.Pointer(&bTmp[0]))
		if err = syscall.GetAdaptersInfo(adtr, &length); err != nil {
			return nil, os.NewSyscallError("GetAdaptersInfo", err)
		}
	}

	return &IpAdapterInfo{adtr}, nil
}

func Demo() {
	ifs, err := net.Interfaces()
	if err != nil {
		return
	}

	for _, fs := range ifs {
		log.Println(fs.Name, fs.Index)
	}
	log.Println()

	ai, err := GetAdaptersInfo()
	if err != nil {
		return
	}

	result := ai.GetAllPDHCounterName()
	for k, v := range result {
		log.Printf("[%v] [%v]", k, v)
	}
}
